#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace UTable.Objects.Handlers
{
	public class PhysicsTracker
	{
		#region Private Fields

		private List<MovementPoint> _previousPoints = new List<MovementPoint>();

		private class MovementPoint
		{
			public MovementPoint(Point position, double time, Vector velocity)
			{
				Position = position;
				Time = time;
				Velocity = velocity;
			}

			public Point Position { get; set; }

			/// <summary>
			/// Measured in millisecond
			/// </summary>
			public double Time { get; set; }

			/// <summary>
			/// measured in pixels per second
			/// </summary>
			public Vector Velocity { get; set; }
		}

		#endregion

		#region Properties

		public bool TrackVelocity { get; set; }

		public double MinVelocityIntervalTime { get; set; }	// measured in milliseconds

		#endregion

		#region Constructor

		public PhysicsTracker()
		{
			MinVelocityIntervalTime = 100;
			TrackVelocity = false;
		}

		#endregion

		#region Public Methods

		public static double DecelerationTime(double velocity, double deceleration)
		{
			return Math.Abs(velocity) / deceleration;
		}

		public static double DecelerationDistance(double velocity, double deceleration)
		{
			return 0.5 * velocity * velocity / deceleration;
		}

        public static double DecelerationDistance(double velocity, double deceleration, double timeLimit)
        {
            if (timeLimit > DecelerationTime(velocity, deceleration))
                return DecelerationDistance(velocity, deceleration);
            return 0.5 * (velocity + velocity - deceleration * timeLimit) * timeLimit;
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="velocity"></param>
		/// <param name="deceleration"></param>
		/// <param name="distance"></param>
		/// <param name="time"></param>
		/// <param name="moveDistance"></param>
		/// <returns>the velocity when get the distance, 0 means not get to the distance</returns>
		public static double DecelerateSomeDistance(double velocity, double deceleration, double distance, out double time, out double moveDistance)
		{
			double wholeDistance = DecelerationDistance(velocity, deceleration);
			if (wholeDistance <= distance)
			{
				time = DecelerationTime(velocity, deceleration);
				moveDistance = wholeDistance;
				return 0;
			}
			else
			{
				time = Math.Sqrt(2 * distance / deceleration);
				moveDistance = distance;
				return velocity - time * deceleration;
			}
		}

		// public static double 

		public Vector CurrentVelocity()
		{
			if (_previousPoints.Count == 0)
				return new Vector(double.NaN, double.NaN);
			else
				return _previousPoints[_previousPoints.Count - 1].Velocity;
		}

		/// <summary>
		/// calculate the velocity
		/// </summary>
		/// <returns></returns>
		public Vector UpdateVelocity(Point newPosition, double timeStamp)
		{
			if (!TrackVelocity)
				return new Vector(double.NaN, double.NaN);

			// firstly get the movement point that apart from the current point in MinVelocityIntervalTime
			double timeInterval = 0;
			Point position = new Point();
			for (int i = _previousPoints.Count - 1; i >= 0; i--)
			{
				double interval = timeStamp - _previousPoints[i].Time;
				if (interval > MinVelocityIntervalTime)
				{
					timeInterval = interval;
					position = _previousPoints[i].Position;
					break;
				}
			}
			Vector velocity = new Vector(0, 0);
			if (timeInterval > 0)
			{
				velocity.X = (newPosition.X - position.X) / timeInterval * 1000;
				velocity.Y = (newPosition.Y - position.Y) / timeInterval * 1000;
			}

			MovementPoint point = new MovementPoint(newPosition, timeStamp, velocity);
			this._previousPoints.Add(point);

			return velocity;
		}

		#endregion
	}
}
